`include "CacheDefines.svh"
`include "defines.svh"
`define CLOG2(x) ((x <= 1) || (x > 512)) ? 0 : (x <= 2) ? 1 : (x <= 4) ? 2 : (x <= 8) ? 3 : (x <= 16) ? 4 : (x <= 32) ? 5 : (x <= 64) ? 6 : (x <= 128) ? 7: (x <= 256) ? 8: (x <= 512) ? 9 : 0


module Dcache #(
    parameter STORE_BUFFER_SIZE = 32,
    parameter DATA_WIDTH    = 32,//cache??cpu ?????????????data_width
    parameter LINE_WORD_NUM = 4,//cache line???? ??????????
    parameter ASSOC_NUM     = 4,//assoc_num???????
    parameter WAY_SIZE      = 4*1024*8,//?????cache ?????????way_size bit
    parameter SET_NUM       = WAY_SIZE/(LINE_WORD_NUM*DATA_WIDTH) //256
) (
    input logic clk,
    input logic resetn,
    
    AXI_UDCACHE_Interface axi_ubus,
    CPU_DBus_Interface cpu_bus,
    AXI_DBus_Interface axi_bus

);

localparam int unsigned BYTES_PER_WORD = 4;
localparam int unsigned INDEX_WIDTH    = $clog2(SET_NUM) ;  //8
localparam int unsigned OFFSET_WIDTH   = $clog2(LINE_WORD_NUM*BYTES_PER_WORD);  //4
localparam int unsigned TAG_WIDTH      = 32-INDEX_WIDTH-OFFSET_WIDTH ;  //20
typedef logic [DATA_WIDTH-1:0] data_t;
typedef struct packed {
    logic valid;
    logic [TAG_WIDTH-1:0]tag;
} TagVType;

typedef struct packed {
    logic [31:0] address;
    logic [31:0] data;
    logic [3:0]  wstrb;
} UncacheStoreTpye;

/*function logic clog2(
    input logic [1:0] hit
);
    return{
        (hit[1])?1'b1:1'b0
    };
endfunction*/
logic [1:0] MuxHit;
//assign MuxHit = (hit[3]) ? 2'b11 : (hit[2]) ? 2'b10 : (hit[1]) ? 2'b01 : (hit[0]) ? 2'b00 : 2'b00;
always_comb begin
    case(hit)
        4'b1000:begin
            MuxHit = 2'b11;
        end
        4'b0100:begin
            MuxHit = 2'b10;
        end
        4'b0010:begin
            MuxHit = 2'b01;
        end
        4'b0001:begin
            MuxHit = 2'b00;
        end
        default:begin
            MuxHit = 2'b00;
        end
    endcase
end
typedef enum logic [3:0]
{
    MISSDIRTY,
    WRITEBACK,
    LOOKUP,
    MISSCLEAN,
    REFILL,
    REFILLDONE,
    HITVICTIM
} StateType;

typedef enum logic [2:0]
{
    WIDLE,
    WSTORE
} WriteBackState;

typedef enum logic [2:0]
{
    UIDLE,
    UWAIT, //wati axi read
    UREAD,   //wait axi read valid
    UDONE
} UncacheStateType;

typedef struct packed {
    logic valid;
    logic op;
    logic [TAG_WIDTH-1:0] tag;
    logic [INDEX_WIDTH-1:0] index;
    logic [OFFSET_WIDTH-1:0]offset;
    logic [3:0]wstrb;   //write enable
    logic [31:0]wdata;  
    logic [2:0]LoadType;
    logic isCache;
} RequsetType;

typedef struct packed {//
    logic [ASSOC_NUM-1:0] hit;//the hit way
    logic [INDEX_WIDTH-1:0] index;
    logic [OFFSET_WIDTH-1:0] offset;
    logic [DATA_WIDTH-1:0] wdata;
} StoreType;

StateType state,state_next;
WriteBackState wstate,wstate_next;
UncacheStateType ustate,ustate_next;

StoreType store_buffer;

logic [DATA_WIDTH-1:0] uncache_rdata;

logic [INDEX_WIDTH-1:0] r_addr_index,w_addr_index,tagv_addr_index;
//r_addr_index:used in read and REFILL     w_addr_index:used in store

//fifo
UncacheStoreTpye fifo_din;//input
logic fifo_wr_en;
logic fifo_rd_en;

logic fifo_rd_rst_busy;// output
logic fifo_full;
logic fifo_empty;
UncacheStoreTpye fifo_dout;
logic fifo_data_valid;
logic fifo_wr_ack;
logic fifo_wr_rst_busy;




logic[31:0]req_buffer_phy_addr;
assign req_buffer_phy_addr={req_buffer.tag , req_buffer.index, req_buffer.offset};

RequsetType req_buffer;
logic req_buffer_en;

logic [$clog2(ASSOC_NUM)-1:0] lru[SET_NUM-1:0];
logic [ASSOC_NUM-1:0] hit;
logic cache_hit;

TagVType [ASSOC_NUM-1:0] tagv_rdata_buffer;
logic tagv_buffer_wen;

//the rewrite way
logic [$clog2(ASSOC_NUM)-1:0]rewrite_way;
assign rewrite_way = lru[req_buffer.index];

//******random rewrite method
/*
logic[$clog2(ASSOC_NUM)-1:0]lru;
always_ff @(posedge clk) begin
    if(resetn == `RstEnable) begin
        lru <= '0;
    end else 
        lru=lru+1;
end
assign rewrite_way=lru;
*/

//rdata
data_t cache_rdata_assoc_off[ASSOC_NUM-1:0][LINE_WORD_NUM-1:0];//32*4*4 all data with the same Index
logic[DATA_WIDTH-1:0] cache_rdata_assoc[ASSOC_NUM-1:0];//32*4 the same Index and the same Offset
logic[DATA_WIDTH-1:0] cache_rdata;//when read and store has the same addr and the last rdata
logic[DATA_WIDTH-1:0] cache_rdata_end;//32 the same Index and same Offset and same Way:data_end 
logic busy;
//cpu_bus
assign cpu_bus.busy   = busy;
assign cpu_bus.rdata  = (req_buffer.valid)?cache_rdata_end:'0;

//axi_bus
assign axi_bus.rd_req  = (state == MISSCLEAN) ? 1'b1:1'b0;
assign axi_bus.rd_addr = {req_buffer.tag , req_buffer.index, {OFFSET_WIDTH{1'b0}}};
assign axi_bus.wr_req  = (state == MISSDIRTY) ? 1'b1:1'b0;
assign axi_bus.wr_addr = {tagv_rdata_buffer[rewrite_way].tag,req_buffer.index,{OFFSET_WIDTH{1'b0}}};
generate;
    for (genvar i=0; i<LINE_WORD_NUM; i++) begin
        assign  axi_bus.wr_data[32*(i+1)-1:32*(i)] = cache_rdata_assoc_off[rewrite_way][i];
    end
endgenerate

assign axi_ubus.rd_req   = (ustate == UWAIT && fifo_empty) ? 1'b1:1'b0;
assign axi_ubus.rd_addr  = {req_buffer.tag , req_buffer.index, req_buffer.offset};
assign axi_ubus.wr_req   = (fifo_empty || fifo_rd_rst_busy) ? 1'b0:1'b1;
assign axi_ubus.wr_addr  = {fifo_dout.address}; 
assign axi_ubus.wr_data  = {fifo_dout.data};
assign axi_ubus.wr_wstrb = fifo_dout.wstrb;
assign axi_ubus.LoadType = req_buffer.LoadType;

//dirty bit , tag table and data table
//TagV
TagVType[ASSOC_NUM-1:0]tagv_rdata; //tagv table of 4 ways in the same Index
TagVType tagv_wdata;
logic [ASSOC_NUM-1:0] tag_we;

logic [INDEX_WIDTH-1:0] dirty_addr_index; //when MISSDIRTY , is used to keep the addr which is going to REFILL
logic [ASSOC_NUM-1:0]dirty_rdata; //dirty bit of 4 ways
logic dirty_wdata;
logic [ASSOC_NUM-1:0]dirty_we; //choose to renew dirty    
logic [ASSOC_NUM-1:0]tagv_we;

//Data
data_t data_wdata[LINE_WORD_NUM-1:0];//32*4 data from AXI when REFILL
logic[ASSOC_NUM-1:0][LINE_WORD_NUM-1:0] data_we; //4*4  when store: point to a certain word
logic data_read_en;//read enable

//temp
typedef logic dirty;

generate;
    for (genvar i = 0;i<ASSOC_NUM ;i++ ) begin
        simple_port_lutram #(
            .SIZE(SET_NUM),
            .dtype(logic)
        )mem_dirty(
            .clka(clk),
            .rsta(~resetn),
            .ena(1'b1),
            .wea(dirty_we[i]),
            .addra(dirty_addr_index),
            .dina(dirty_wdata),
            .douta(dirty_rdata[i])            
        );

        simple_port_lutram  #(
            .SIZE(SET_NUM),
            .dtype(TagVType)
        ) mem_tag(
            .clka(clk),
            .rsta(~resetn),
            .ena(1'b1),
            .wea(tagv_we[i]),
            .addra(tagv_addr_index),
            .dina(tagv_wdata),
            .douta(tagv_rdata[i])
        );
        for (genvar j=0; j<LINE_WORD_NUM; ++j) begin
        simple_port_ram #(
            .SIZE(SET_NUM)
        )mem_data(
            .clk(clk),
            .rst(~resetn),

            //write
            .ena(1'b1),
            .wea(data_we[i][j]),
            .addra(w_addr_index),
            .dina(data_wdata[j]),

            //read
            .enb(data_read_en),
            .addrb(r_addr_index),
            .doutb(cache_rdata_assoc_off[i][j])
        );
    end
    end
endgenerate


//PLRU
generate; 
    for (genvar  i=0; i<SET_NUM; i++) begin
        PLRU #(
            .ASSOC_NUM(ASSOC_NUM)
        ) plru_reg(
            .clk(clk),
            .resetn(resetn),
            .access(hit),
            .update(req_buffer.valid &&i[INDEX_WIDTH-1:0] == req_buffer.index),
            .lru(lru[i])
        );
    end
endgenerate


//hit
generate;
    for (genvar i=0; i<ASSOC_NUM; i++) begin
        assign hit[i] = (tagv_rdata_buffer[i].valid & (req_buffer.tag == tagv_rdata_buffer[i].tag) & req_buffer.isCache) ? 1'b1:1'b0;
    end
endgenerate
//choose by offset
generate;
    for (genvar i=0; i<ASSOC_NUM; i++) begin
        assign cache_rdata_assoc[i] = cache_rdata_assoc_off[i][req_buffer.offset[OFFSET_WIDTH-1:2]];
    end
endgenerate
//forword
assign cache_rdata_end =  (ustate == UDONE )? uncache_rdata: cache_rdata;
//hit
assign cache_hit = |hit;
//addr
//choose between REFILL and hit
assign r_addr_index = (state == REFILLDONE || state == REFILL || cpu_bus.stall)? req_buffer.index : cpu_bus.index;



//choose between REFILL and hit
assign w_addr_index = (state == REFILL)?req_buffer.index : store_buffer.index;
//when REFILL
assign tagv_addr_index = (state == REFILLDONE || state == REFILL) ? req_buffer.index :cpu_bus.index;

//busy
logic busy_cache;
logic busy_uncache_read;
logic busy_uncache_write;//when fifo is full


assign busy_cache          = (req_buffer.valid & ~cache_hit & req_buffer.isCache) ? 1'b1:1'b0;
assign busy_uncache_read   = (ustate == UIDLE  || ustate == UDONE) ? 1'b0 : 1'b1;
assign busy_uncache_write  = (fifo_full) ? 1'b1:1'b0;
assign busy                = busy_cache | busy_uncache_read | busy_uncache_write;

assign tagv_buffer_wen        = (state == REFILLDONE) ? 1'b1:(cpu_bus.stall)?1'b0:1'b1;

assign req_buffer_en  = (cpu_bus.stall)? 1'b0:1'b1 ;

generate;//
    for (genvar i=0; i<LINE_WORD_NUM; i++) begin
        assign data_wdata[i] = (state == REFILL) ? axi_bus.ret_data[32*(i+1)-1:32*(i)] : store_buffer.wdata;
    end
endgenerate
assign tagv_wdata     = {1'b1,req_buffer.tag};
assign data_read_en   = (state == REFILLDONE ) ? 1'b1  : (cpu_bus.stall) ? 1'b0 : 1'b1;

assign dirty_wdata    = (state == REFILL)? 1'b0 : 1'b1;
assign dirty_addr_index     = req_buffer.index;

always_comb begin
    if(state == REFILL)begin
        dirty_we = '0;
        dirty_we[rewrite_way] = 1'b1;
    end else if(req_buffer.valid & req_buffer.op & req_buffer.isCache)begin
        dirty_we = hit;
    end else begin
        dirty_we = '0;
    end
end
//forward
always_comb begin
    cache_rdata = (|data_we[MuxHit]  & 
                        store_buffer.hit == hit & 
                        {store_buffer.index,store_buffer.offset[OFFSET_WIDTH-1:2]} == 
                        {req_buffer.index,req_buffer.offset[OFFSET_WIDTH-1:2]}) ?
                        store_buffer.wdata :
                        cache_rdata_assoc[MuxHit];
end
//tagv_we
always_comb begin
    if (state == REFILL) begin
        tagv_we = '0;
        tagv_we[rewrite_way] =1'b1;
    end else begin
        tagv_we = '0;
    end
end
//data_we
logic[1:0] MuxHit_store;
//assign MuxHit_store = store_buffer.hit[3] ? 2'b11 : store_buffer.hit[2] ? 2'b10 : store_buffer.hit[1] ? 2'b01 : store_buffer.hit[0] ? 2'b00 : 2'b00; 
always_comb begin
    case(store_buffer.hit)
        4'b1000:begin
            MuxHit_store = 2'b11;
        end
        4'b0100:begin
            MuxHit_store = 2'b10;
        end
        4'b0010:begin
            MuxHit_store = 2'b01;
        end
        4'b0001:begin
            MuxHit_store = 2'b00;
        end
        default:begin
            MuxHit_store = 2'b00;
        end
    endcase
end
always_comb begin
    data_we = '0;
    if(state == REFILL) begin
        data_we[rewrite_way] ='1;
    end else if(wstate == WSTORE)begin
        data_we[MuxHit_store][store_buffer.offset[OFFSET_WIDTH-1:2]] = 1'b1;
    end else begin
        data_we = '0;
    end   
end

logic [31:0]store_wdata_end;
assign store_wdata_end[7:0]=req_buffer.wstrb[0] ? req_buffer.wdata[7:0] : cache_rdata[7:0];
assign store_wdata_end[15:8]=req_buffer.wstrb[1] ? req_buffer.wdata[15:8] : cache_rdata[15:8];
assign store_wdata_end[23:16]=req_buffer.wstrb[2] ? req_buffer.wdata[23:16] : cache_rdata[23:16];
assign store_wdata_end[31:24]=req_buffer.wstrb[3] ? req_buffer.wdata[31:24] : cache_rdata[31:24];

always_ff @( posedge clk ) begin
    if ((resetn == 1'b0) ) begin
        store_buffer <= '0;
    end else if(~cpu_bus.stall && req_buffer.valid==1'b1)begin
        store_buffer <= {hit,req_buffer.index,req_buffer.offset,store_wdata_end}; 
    end else if (~cpu_bus.stall && req_buffer.valid==1'b0) begin
        store_buffer <= '0;
    end
end
//req_buffer
always_ff @(posedge clk) begin
    if (resetn == 1'b0) begin
        req_buffer <='0;
    end else if(req_buffer_en) begin
        req_buffer <= {cpu_bus.valid,cpu_bus.op,cpu_bus.tag,cpu_bus.index,cpu_bus.offset,cpu_bus.wstrb,cpu_bus.wdata,cpu_bus.LoadType,cpu_bus.isCache};
    end else begin
        req_buffer <= req_buffer;
    end
end
//renew uncache data
always_ff @( posedge clk ) begin
    if (axi_ubus.ret_valid) begin
        uncache_rdata <= axi_ubus.ret_data;
    end else begin
        uncache_rdata <= uncache_rdata;
    end
end

//tagv buffer
generate;
    for (genvar  i=0; i<ASSOC_NUM; i++) begin
    always_ff @( posedge clk ) begin
        if (tagv_buffer_wen) begin
            tagv_rdata_buffer[i] <= tagv_rdata[i];
        end else begin
            tagv_rdata_buffer[i] <= tagv_rdata_buffer[i];    
        end
    end        
    end
endgenerate

always_ff @( posedge clk ) begin 
    if (resetn == 1'b0) begin
        state <= LOOKUP;
    end else begin
        state <= state_next;
    end
end

always_comb begin 
    state_next =LOOKUP;

    unique case (state)
        LOOKUP:begin
            if ( req_buffer.valid) begin
                if (req_buffer.isCache == 1'b0 ) begin
                    state_next = LOOKUP;
                end else begin
                if (cache_hit) begin
                    state_next = LOOKUP;
                end else begin
                    if (dirty_rdata[rewrite_way]) begin
                        state_next = MISSDIRTY ;
                    end else begin
                        state_next = MISSCLEAN ;
                    end
                end
                end      
            end else begin
                state_next = LOOKUP;
            end
        /*
        LOOKUP:begin
            if ( req_buffer.valid) begin
                if (req_buffer.isCache == 1'b0 ) begin
                    state_next = LOOKUP;
                end else begin
                if (cache_hit) begin
                    state_next = LOOKUP;
                end else begin
                    state_next = LOOKVC;
                    end
                end
                end      
            end else begin
                state_next = LOOKUP;
            end
        
        */
        /*
        LOOKVC:begin
            if (VC_hit)
                state_next = EXCHANGE; 
            else if (VC_dirty_rdata[rewrite_way]) begin
                        state_next = MISSDIRTY ;
                    end else begin
                        state_next = MISSCLEAN ;
                    end
                end
        */
        /*
        EXCHANGE:begin
            state_next = LOOKUP;
        end
            
        */
        end
        MISSCLEAN:begin
            if (axi_bus.rd_rdy) begin
                state_next = REFILL;
            end else begin
                state_next = MISSCLEAN;
            end
        end
        REFILL:begin
            if (axi_bus.ret_valid) begin
                state_next = REFILLDONE;
            end else begin
                state_next = REFILL;
            end
        end
        REFILLDONE:begin
                state_next = LOOKUP;
        end
        MISSDIRTY:begin
            if (axi_bus.wr_rdy) begin
                state_next = WRITEBACK;
            end else begin
                state_next =  MISSDIRTY;
            end
        end
        WRITEBACK:begin
            if (axi_bus.wr_valid) begin
                state_next = MISSCLEAN;
            end else begin
                state_next = WRITEBACK;
            end
        end
        default: begin
            state_next =LOOKUP;
        end
    endcase
end
//wb_store_state
always_ff @(posedge clk) begin
    if (resetn == `RstEnable) begin
        wstate <= WIDLE;
    end else if(~cpu_bus.stall)begin
        wstate <= wstate_next;
    end
end

always_comb begin
    if (req_buffer.valid & req_buffer.op & cache_hit & ~cpu_bus.stall) begin
        wstate_next = WSTORE;
    end else begin
        wstate_next = WIDLE;
    end

end


always_ff @( posedge clk ) begin
    if (resetn == 1'b0) begin
        ustate <= UIDLE;
    end else begin
        ustate <= ustate_next;
    end
end
///uuuuuuuu
always_comb begin
    ustate_next = ustate;
    case (ustate)
        UIDLE:begin
            if (cpu_bus.valid & (~cpu_bus.isCache) & req_buffer_en) begin 
                if (cpu_bus.op==1'b0) begin
                    ustate_next = UWAIT;
                end else begin
                    ustate_next = UIDLE;
                end
            end 
        end
        UWAIT:begin
            if (axi_ubus.rd_rdy && fifo_empty) begin
                ustate_next = UREAD;
            end else begin
                ustate_next = UWAIT;
            end

        end
        UREAD:begin
            if (axi_ubus.ret_valid) begin
                ustate_next = UDONE;
            end else begin
                ustate_next = UREAD;
            end
        end
        UDONE:begin
            if (cpu_bus.stall) begin
                ustate_next = UDONE;
            end else begin
                if (cpu_bus.valid & (~cpu_bus.isCache) & req_buffer_en) begin 
                    if (cpu_bus.op==1'b0 ) begin
                        ustate_next = UWAIT;
                    end else begin
                        ustate_next = UIDLE;
                    end
                end
                else begin
                    ustate_next = UIDLE;
                end
            end
        end
        default:begin
            ustate_next = ustate;           
        end
    endcase
end




assign fifo_din   = {req_buffer.tag,req_buffer.index,req_buffer.offset,req_buffer.wdata,req_buffer.wstrb};
assign fifo_wr_en = (cpu_bus.stall || fifo_wr_rst_busy || fifo_full || (~(req_buffer.valid & req_buffer.op & (~req_buffer.isCache))) ) ?  1'b0 : 1'b1;//????????? ??????? fifo??? ????uncache??????
assign fifo_rd_en = (axi_ubus.wr_rdy && (!fifo_empty) && (!fifo_rd_rst_busy)) ? 1'b1 :1'b0;//??? ???? 




FIFO #(
  .SIZE(STORE_BUFFER_SIZE),
  .dtype(UncacheStoreTpye),
  .LATENCY (0)
)
FIFO_dut (
  .clk (clk ),
  .rst (~resetn),
  .din (fifo_din ),
  .rd_en (fifo_rd_en ),
  .wr_en (fifo_wr_en ),
  .rd_rst_busy (fifo_rd_rst_busy ),
  .full (fifo_full ),
  .empty (fifo_empty ),
  .dout (fifo_dout ),
  .data_valid (fifo_data_valid ),
  .wr_ack (fifo_wr_ack ),
  .wr_rst_busy  (fifo_wr_rst_busy)
);

//victim cache

/*
assign victim_hit=(cpu_bus.stall) ? (victim_tagvindex_rdata[TAG_WIDTH+INDEX_WIDTH] & ({req_buffer.tag,req_buffer.index} == victim_data_rdata[TAG_WIDTH+INDEX_WIDTH-1:0]) & req_buffer.isCache ) ? 1'b1 : 1'b0  
        : (victim_tagvindex_rdata[TAG_WIDTH+INDEX_WIDTH]  & ({cpu_bus.tag,cpu_bus.index} == victim_data_rdata[TAG_WIDTH+INDEX_WIDTH-1:0]) & cpu_bus.isCache) ? 1'b1:1'b0;
*/
/*
//assign r_addr_index = (state == REFILLDONE || state == REFILL || cpu_bus.stall || state == HITVICTIM)? req_buffer.index : cpu_bus.index;


//victim
logic victim_we;
logic [LINE_WORD_NUM-1:0][DATA_WIDTH-1:0] victim_data_rdata,victim_data_wdata;
logic[TAG_WIDTH+INDEX_WIDTH+1-1:0] victim_tagvindex_wdata,victim_tagvindex_rdata;
logic victim_hit;
logic pipe_victim_hit;


Victim_Cache #(
  .SIZE(8),
  .INDEX_WIDTH(INDEX_WIDTH ),
  .TAG_WIDTH(TAG_WIDTH ),
  .ASSOC_NUM(ASSOC_NUM ),
  .LINE_WORD_NUM (
      LINE_WORD_NUM )
)
Victim_Cache_dut (
  .clk (clk ),
  .resetn (resetn ),
  .index (r_addr_index ),
  .data_read_en (data_read_en ),
  .we (victim_we ),
  .tagvindex_wdata (victim_tagvindex_wdata ),
  .data_wdata (victim_data_wdata ),
  .data_rdata (victim_data_rdata ),
  .tagvindex_rdata  (victim_tagvindex_rdata)
);
*/









endmodule

